// SPDX-License-Identifier: GPL-2.0-only
/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2022-2023. All rights reserved.
 * Description: tty function extension of liblinux
 * Author: Huawei OS Kernel Lab
 * Create: Fri Sep 23 10:45:12 2022
 */

#include <linux/types.h>
#include <linux/errno.h>
#include <linux/signal.h>
#include <linux/tty.h>
#include <linux/bitops.h>
#include <linux/spinlock.h>
#include "tty.h"

#define UART_ASCII_CTRL_X 0x18 /* ASCII value of Ctrl-X */
#define UART_ASCII_CTRL_T 0x14 /* ASCII value of Ctrl-T */

static void (*receive_buf)(struct tty_struct *, const unsigned char *, char *, int);
static int (*receive_buf2)(struct tty_struct *, const unsigned char *, char *, int);

struct special_char_info {
	DECLARE_BITMAP(char_map, 256);
};

static void tty_get_special_char(struct tty_struct *tty,
				const unsigned char *cp,
				char *fp,
				int count,
				struct special_char_info *special_char)
{
	char flag = TTY_NORMAL;

	down_read(&tty->termios_rwsem);
	while (count--) {
		if (fp)
			flag = *fp++;

		if (likely(flag == TTY_NORMAL)) {
			unsigned char c = *cp;

			set_bit(c, special_char->char_map);
		}
		cp++;
	}
	up_read(&tty->termios_rwsem);
}

static void tty_spcecial_char_process(struct tty_struct *tty,
					struct special_char_info *special_char)
{
#ifdef CONFIG_LIBLINUX_TTY_CTRL_SYS_PAUSE
	if (test_bit(UART_ASCII_CTRL_X, special_char->char_map)) {
		if (strcmp(tty->name, CONFIG_LIBLINUX_TTY_CTRL_PORT) == 0) {
			tty_info(tty, "ctrl-x received.\n");
			liblinux_pal_sys_shutdown();
		}
	}
#endif
#ifdef CONFIG_LIBLINUX_TTY_CTRL_TRIGGER_SNAPSHOT
	if (test_bit(UART_ASCII_CTRL_T, special_char->char_map)) {
		if (strcmp(tty->name, CONFIG_LIBLINUX_TTY_CTRL_PORT) == 0)
			liblinux_pal_sys_snapshot();
	}
#endif
}

static void n_tty_receive_buf_ext(struct tty_struct *tty, const unsigned char *cp,
			      char *fp, int count)
{
	struct special_char_info special_char;

	bitmap_zero(special_char.char_map, 256);
	tty_get_special_char(tty, cp, fp, count, &special_char);
	if (receive_buf)
		receive_buf(tty, cp, fp, count);

	tty_spcecial_char_process(tty, &special_char);
}

static int n_tty_receive_buf2_ext(struct tty_struct *tty, const unsigned char *cp,
			      char *fp, int count)
{
	int ret;
	struct special_char_info special_char;

	bitmap_zero(special_char.char_map, 256);
	tty_get_special_char(tty, cp, fp, count, &special_char);
	if (receive_buf2)
		ret = receive_buf2(tty, cp, fp, count);

	tty_spcecial_char_process(tty, &special_char);

	return ret;
}

static struct tty_ldisc_ops ops;

void tty_set_receive_buf(void)
{
	n_tty_inherit_ops(&ops);
	receive_buf = ops.receive_buf;
	receive_buf2 = ops.receive_buf2;

	ops.receive_buf = n_tty_receive_buf_ext;
	ops.receive_buf2 = n_tty_receive_buf2_ext;

	tty_register_ldisc(N_TTY, &ops);
}
